package com.mysteel.monitor.core.tools;

import sun.jvm.hotspot.HotSpotAgent;
import sun.jvm.hotspot.debugger.DebuggerException;
import sun.jvm.hotspot.debugger.JVMDebugger;
import sun.jvm.hotspot.runtime.VM;

import java.io.PrintStream;

// generic command line or GUI tool.
// override run & code main as shown below.

public abstract class Tool implements Runnable {
    private HotSpotAgent agent;
    private JVMDebugger jvmDebugger;
    private int debugeeType;

    // debugeeType is one of constants below
    protected static final int DEBUGEE_PID    = 0;
    protected static final int DEBUGEE_CORE   = 1;
    protected static final int DEBUGEE_REMOTE = 2;

    public Tool() {
    }

    public Tool(JVMDebugger d) {
        jvmDebugger = d;
    }

    public String getName() {
        return getClass().getName();
    }

    protected boolean needsJavaPrefix() {
        return true;
    }

    protected void setAgent(HotSpotAgent a) {
        agent = a;
    }

    protected void setDebugeeType(int dt) {
        debugeeType = dt;
    }

    protected HotSpotAgent getAgent() {
        return agent;
    }

    protected int getDebugeeType() {
        return debugeeType;
    }

    protected void printUsage() {
        String name = null;
        if (needsJavaPrefix()) {
            name = "java " + getName();
        } else {
            name = getName();
        }
        System.out.println("Usage: " + name + " [option] <pid>");
        System.out.println("\t\t(to connect to a live java process)");
        System.out.println("   or " + name + " [option] <executable> <core>");
        System.out.println("\t\t(to connect to a core file)");
        System.out.println("   or " + name + " [option] [server_id@]<remote server IP or hostname>");
        System.out.println("\t\t(to connect to a remote debug server)");
        System.out.println();
        System.out.println("where option must be one of:");
        printFlagsUsage();
    }

    protected void printFlagsUsage() {
        System.out.println("    -h | -help\tto print this help message");
    }

    protected void usage() {
        printUsage();
    }

   /*
      Derived class main should be of the following form:

      public static void main(String[] args) {
         <derived class> obj = new <derived class>;
         obj.execute(args);
      }

   */

    protected void execute(String[] args) {
        int returnStatus = 1;

        try {
            returnStatus = start(args);
        } finally {
            stop();
        }

//        // Exit with 0 or 1
//        System.exit(returnStatus);
    }

    public void stop() {
        if (agent != null) {
            agent.detach();
        }
    }

    private int start(String[] args) {

        if ((args.length < 1) || (args.length > 2)) {
            usage();
            return 1;
        }

        // Attempt to handle -h or -help or some invalid flag
        if (args[0].startsWith("-h")) {
            usage();
            return 0;
        } else if (args[0].startsWith("-")) {
            usage();
            return 1;
        }

        PrintStream err = System.err;
        PrintStream out = System.out;

        int pid = 0;
        String coreFileName   = null;
        String executableName = null;
        String remoteServer   = null;

        switch (args.length) {
            case 1:
                try {
                    pid = Integer.parseInt(args[0]);
                    debugeeType = DEBUGEE_PID;
                } catch (NumberFormatException e) {
                    // try remote server
                    remoteServer = args[0];
                    debugeeType  = DEBUGEE_REMOTE;
                }
                break;

            case 2:
                executableName = args[0];
                coreFileName   = args[1];
                debugeeType    = DEBUGEE_CORE;
                break;

            default:
                usage();
                return 1;
        }

        agent = new HotSpotAgent();
        try {
            switch (debugeeType) {
                case DEBUGEE_PID:
                    out.println("Attaching to process ID " + pid + ", please wait...");
                    agent.attach(pid);
                    break;

                case DEBUGEE_CORE:
                    out.println("Attaching to core " + coreFileName +
                            " from executable " + executableName + ", please wait...");
                    agent.attach(executableName, coreFileName);
                    break;

                case DEBUGEE_REMOTE:
                    out.println("Attaching to remote server " + remoteServer + ", please wait...");
                    agent.attach(remoteServer);
                    break;
            }
        }
        catch (DebuggerException e) {
            switch (debugeeType) {
                case DEBUGEE_PID:
                    err.print("Error attaching to process: ");
                    break;

                case DEBUGEE_CORE:
                    err.print("Error attaching to core file: ");
                    break;

                case DEBUGEE_REMOTE:
                    err.print("Error attaching to remote server: ");
                    break;
            }
            if (e.getMessage() != null) {
                err.println(e.getMessage());
                e.printStackTrace();
            }
            err.println();
            return 1;
        }

        out.println("Debugger attached successfully.");
        startInternal();
        return 0;
    }

    // When using an existing JVMDebugger.
    public void start() {

        if (jvmDebugger == null) {
            throw new RuntimeException("Tool.start() called with no JVMDebugger set.");
        }
        agent = new HotSpotAgent();
        agent.attach(jvmDebugger);
        startInternal();
    }

    // Remains of the start mechanism, common to both start methods.
    private void startInternal() {

        PrintStream out = System.out;
        VM vm = VM.getVM();
        if (vm.isCore()) {
            out.println("Core build detected.");
        } else if (vm.isClientCompiler()) {
            out.println("Client compiler detected.");
        } else if (vm.isServerCompiler()) {
            out.println("Server compiler detected.");
        } else {
            throw new RuntimeException("Fatal error: "
                    + "should have been able to detect core/C1/C2 build");
        }

        String version = vm.getVMRelease();
        if (version != null) {
            out.print("JVM version is ");
            out.println(version);
        }

        run();
    }
}
